﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.IO;
using System.Runtime.Serialization;
using System.Linq;

namespace Zero.Common
{
    /// <summary>
    /// Base object for framework data entities.
    /// </summary>
    /// <remarks>
    /// It is used as data object in database, as transport object between client and server and can be used as part of binding for GUI.
    /// It offers propety change notification. Property validation through DataAnotations and <see cref="IDataErrorInfo"/>. And explicit implementation of <see cref="ICloneable"/>.
    /// </remarks>
    [DataContract(IsReference = true)]
    public abstract class EntityObject :  INotifyPropertyChanged, IDataErrorInfo, ICloneable
    {
        #region PropertyChanged

        /// <summary>
        /// Raises a <see cref="PropertyChanged"/> event.
        /// </summary>
        /// <param name="propertyName"></param>
        protected virtual void OnPropertyChanged(String propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
                PropertyChanged(this, new PropertyChangedEventArgs("IsValid"));
            }
        }

        /// <summary>
        /// Raised when value of any property changes.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        #region SerializationTracking

        /// <summary>
        /// <c>true</c> if object is being serialized.
        /// </summary>
        protected bool IsDeserializing { get; private set; }

        /// <summary>
        /// <c>true</c> if object is being deserialized.
        /// </summary>
        protected bool IsSerializing { get; private set; }

        /// <summary>
        /// For internal use only.
        /// </summary>
        /// <param name="context"></param>
        [OnSerializing]
        [Obsolete("For internal use only.")]
        public void OnSerializingMethod(StreamingContext context)
        {
            IsSerializing = true;
        }

        /// <summary>
        /// For internal use only.
        /// </summary>
        /// <param name="context"></param>
        [OnSerialized]
        [Obsolete("For internal use only.")]
        public void OnSerializedMethod(StreamingContext context)
        {
            IsSerializing = false;
        }

        /// <summary>
        /// For internal use only.
        /// </summary>
        /// <param name="context"></param>
        [OnDeserializing]
        [Obsolete("For internal use only.")]
        public void OnDeserializingMethod(StreamingContext context)
        {
            IsDeserializing = true;
        }

        /// <summary>
        /// For internal use only.
        /// </summary>
        /// <param name="context"></param>
        [OnDeserialized]
        [Obsolete("For internal use only.")]
        public void OnDeserializedMethod(StreamingContext context)
        {
            IsDeserializing = false;
        }

        #endregion

        #region IDataErrorInfo

        string IDataErrorInfo.this[string propertyName]
        {
            get
            {
                // validation based on DataAnotations
                // only for specific property
                var context = new ValidationContext(this, null, null)
                {
                    MemberName = propertyName
                };

                var results = new List<ValidationResult>();
                var value = GetType().GetProperty(propertyName).GetValue(this, null);

                return !Validator.TryValidateProperty(value, context, results)
                    ? string.Join(Environment.NewLine, results.Select(x => x.ErrorMessage))
                    : null;
            }
        }

        string IDataErrorInfo.Error
        {
            get
            {
                // validation based on DataAnotations
                var context = new ValidationContext(this, null, null);
                var results = new List<ValidationResult>();

                return !Validator.TryValidateObject(this, context, results)
                    ? string.Join(Environment.NewLine, results.Select(x => x.ErrorMessage))
                    : null;
            }
        }

        #endregion

        /// <summary>
        /// <c>true</c> when entity is valid according to <see cref="IDataErrorInfo"/>.
        /// </summary>
        public bool IsValid
        {
            get
            {
                return string.IsNullOrWhiteSpace((this as IDataErrorInfo).Error);
            }
        }

        /// <summary>
        /// Returns error of <see cref="IDataErrorInfo"/>.
        /// </summary>
        public string ValidError
        {
            get { return (this as IDataErrorInfo).Error; }
        }

        // simple cloning using XML serialisation.
        object ICloneable.Clone()
        {
            DataContractSerializer serializer = new DataContractSerializer(GetType());

            using (Stream stream = new MemoryStream())
            {
                serializer.WriteObject(stream, this);

                stream.Seek(0, SeekOrigin.Begin);

                return serializer.ReadObject(stream);
            }
        }
    }
}
